<?php
/**
 * Created by PhpStorm.
 * User: rocky
 * Date: 2019-06-29
 * Time: 00:24
 */

namespace app\common\bevhior;


use think\Db;
use think\facade\Cache;

class DbCache
{
    //是否开启自动缓存
    protected $autoCacheOn;
    //缓存时间
    protected $expire;
    public function __construct()
    {
        $this->autoCacheOn = env('DB_AUTO_CACHE',false);
        $this->expire = env('DB_CACHE_EXPIRE',0);
    }

    //清理db数据缓存相关操作
    public function clearDbCache()
    {
        //清理db数据缓存相关操作
        if ($this->autoCacheOn) {
            $updateDeleteFunction = function ($query) {
                $sql = $query->buildSql();
                $resutl = $query->query($sql);
                $pk = $query->getPk();
                $table = $query->getTable();
                $prefix = 'rocky:' . $table . '.';
                foreach ($resutl as $val) {
                    Cache::clear($prefix . $val[$pk]);
                }
                $this->clearCacheJoinTable($table);
            };
            $afterFunction = function ($query) {
                $table = $query->getTable();
                $this->clearCacheJoinTable($table);
                $prefix = 'rocky:' . $table;
                Cache::clear($prefix);
            };
            //新增后
            Db::event('after_insert', $afterFunction);
            //更新后
            Db::event('after_update', $afterFunction);
            //删除后
            Db::event('after_delete', $afterFunction);
            //更新前
            Db::event('before_update', $updateDeleteFunction);
            //删除前
            Db::event('before_delete', $updateDeleteFunction);
        }
    }

    /**
     * 清除相关table的join表缓存
     * @param $table 表名
     */
    protected function clearCacheJoinTable($table)
    {
        if (Cache::has('rocky:join_table')) {
            $cacheJoinData = Cache::get('rocky:join_table');
            $cacheJoinData = unserialize($cacheJoinData);
            foreach ($cacheJoinData as $key => &$val) {

                if (in_array($table, $val)) {
                    Cache::rm($key);
                    unset($cacheJoinData[$key]);
                }
            }

            Cache::set('rocky:join_table', serialize($cacheJoinData));
        }
    }

    //find查询后
    public function afterFind($param)
    {
        if ($this->autoCacheOn) {
            $query = $param['query'];
            $result = $param['result'];
            $sql = $param['sql'];
            $cacheKey = md5($sql);
            $table = $query->getTable();
            $table = $this->getTableName($table);
            if($this->isMathCache($sql)){
                Cache::tag('rocky:' . $table)->set($cacheKey, $result, $this->expire);
            }else{
                if ($this->isCache($sql, $query, 'find')) {
                    $this->cacheJoinTable($query, $cacheKey);
                    $bind = $param['bind'];
                    $connection = $query->getConnection();
                    if (!empty($result)) {
                        $pk = $query->getPk();
                        if (!isset($result[$pk])) {
                            $query->setOption('field', [$query->getPk()]);
                            $query->setOption('limit', 1);
                            $sql = $connection->getBuilder()->select($query);
                            $sql = $connection->getRealSql($sql, $bind);
                            $newResult = $query->query($sql);
                            $id = $newResult[0][$pk];
                        } else {
                            $id = $result[$pk];
                        }
                        $tag = 'rocky:' . $table . '.' . $id;
                        Cache::tag($tag)->set($cacheKey, $result, $this->expire);
                    }
                }
            }
        }
    }
    //判断是否聚合查询
    protected function isMathCache($sql){
        $mathKeyArr = [
            'count(',
            'sum(',
            'min(',
            'avg(',
            'max(',
        ];
        $sql = strtolower($sql);
        foreach ($mathKeyArr as $val) {
            if (strstr($sql, strtolower($val)) !== false) {
                return true;
            }
        }
        return false;
    }
    //find查询前
    public function beforeFind($param)
    {
        if ($this->autoCacheOn) {
            $query = $param['query'];
            $sql = $param['sql'];
            $cacheKey = md5($sql);
            if ($this->isCache($sql, $query, 'find') || $this->isMathCache($sql)) {
                $res = Cache::get($cacheKey);
                return $res;
            }
        }
        return false;
    }

    //select查询前
    public function beforeSelect($query)
    {
        if ($this->autoCacheOn) {
            $connection = $query->getConnection();
            $sql = $connection->getBuilder()->select($query);
            $bind = $query->getBind();
            $sql = $connection->getRealSql($sql, $bind);
            $table = $query->getTable();
            $table = $this->getTableName($table);
            $cacheKey = md5($sql);
            if ($this->isCache($sql, $query)) {
                $this->cacheJoinTable($query, $cacheKey);
                $query->cache($cacheKey, $this->expire, 'rocky:' . $table);
                return $query->getOptions('cache');
            }
        }
        return false;
    }

    /**
     * 缓存join相关的table
     * @param $query query查询对象
     * @param $cacheKey md5的sql
     */
    protected function cacheJoinTable($query, $cacheKey)
    {
        $joins = $query->getOptions('join');
        if (count($joins) > 0) {
            $joinTableArr = [];
            if (Cache::has('rocky:join_table')) {
                $cacheJoinData = Cache::get('rocky:join_table');
                $cacheJoinData = unserialize($cacheJoinData);

            } else {
                $cacheJoinData = [];
            }
            foreach ($joins as $val) {
                $joinTable = array_shift($val);
                $joinTable = $this->getTableName($joinTable);
                array_push($joinTableArr, $joinTable);

            }
            array_push($joinTableArr, $this->getTableName($query->getTable()));
            if (!array_key_exists($cacheKey, $cacheJoinData)) {
                $cacheJoinData[$cacheKey] = $joinTableArr;
                Cache::set('rocky:join_table', serialize($cacheJoinData));
            }
        }
    }

    /**
     * 返回真实数据库表名
     * @param $table 表名 $query->getTable()获得
     * @return int|string|null
     */
    protected function getTableName($table)
    {
        if (is_array($table)) {
            return key($table);
        } else {
            $tables = explode(' ', $table);
            return array_shift($tables);
        }
    }

    /**
     * 判断是否可以缓存
     * @param $sql SQL语句
     * @param null $query query查询对象
     * @param $type find select
     * @return bool
     */
    protected function isCache($sql, $query = null, $type = 'select')
    {
        // mysql关键字
        $filterKeys = [
            'UNION',
            'RAND(',
            'CURDATE(',
            'CURRENT_DATE(',
            'CURRENT_TIME(',
            'CURRENT_TIMESTAMP(',
            'CURTIME(',
            'LOCALTIME(',
            'LOCALTIMESTAMP(',
            'SYSDATE(',

        ];
        $sql = strtolower($sql);
        // 存在关键字不缓存
        foreach ($filterKeys as $val) {
            if (strstr($sql, strtolower($val)) !== false) {
                return false;
            }
        }
        // 存在子查询多个select不缓存
        if (substr_count($sql, 'select') > 1) {
            return false;
        }
        // 设置cacel为null 不做缓存
        if (!is_null($query)) {
            $caches = $query->getOptions('cache');
            if (count($caches) > 0) {
                if (is_null($caches['key'])) {
                    return false;
                }
            }

        }
        // find不存在主键条件不缓存
        if ($type == 'find') {
            $pk = $query->getPk();
            $sql = strtolower($sql);
            $sqlArr = explode('where', $sql);
            if (strstr($sqlArr[1], $pk) === false) {
                return false;
            }
        }
        return true;
    }
}